# Load required packages
library(tidyverse)
library(scales)
library(viridis)
library(knitr)
library(kableExtra)
library(ggridges)
library(patchwork)
# Interactive visualization packages
library(plotly)
library(highcharter)
library(DT)
library(crosstalk)
library(htmltools)
# Set theme for all plots
theme_set(theme_minimal(base_size = 12) +
theme(
plot.title = element_text(face = "bold", size = 14),
plot.subtitle = element_text(color = "gray40"),
legend.position = "bottom",
panel.grid.minor = element_blank()
))
# Custom color palette for fish families
fish_colors <- c(
"Gobiidae" = "#1f77b4",
"Atherinopsidae" = "#ff7f0e",
"Fundulidae" = "#2ca02c",
"Paralichthyidae" = "#d62728",
"Syngnathidae" = "#9467bd",
"Embiotocidae" = "#8c564b",
"Cottidae" = "#e377c2",
"Other" = "#7f7f7f"
)
# Highcharter theme
hc_theme_csm <- hc_theme(
colors = c("#3498db", "#e74c3c", "#2ecc71", "#f39c12", "#9b59b6", "#1abc9c"),
chart = list(
backgroundColor = "#ffffff",
style = list(fontFamily = "Source Sans Pro, sans-serif")
),
title = list(style = list(fontWeight = "bold", fontSize = "18px")),
subtitle = list(style = list(color = "#7f8c8d"))
)
# Interactive map using leaflet
library(leaflet)
leaflet() %>%
addProviderTiles(providers$Esri.WorldImagery) %>%
setView(lng = -119.538, lat = 34.401, zoom = 14) %>%
addMarkers(
lng = -119.538, lat = 34.401,
popup = "<b>Carpinteria Salt Marsh</b><br>~230 acres of coastal wetland<br>Santa Barbara County, CA"
) %>%
addScaleBar(position = "bottomleft")
Carpinteria Salt Marsh is a ~230-acre coastal wetland located in Santa Barbara County, California. It represents one of the few remaining relatively undisturbed tidal wetlands along the Southern California coast and serves as critical habitat for numerous fish species.
Salt marshes provide essential ecosystem services:
This report analyzes data from the SONGS (San Onofre Nuclear Generating Station) Mitigation Monitoring Program collected by UC Santa Barbara’s Marine Science Institute from 2012-2024. The monitoring uses two complementary sampling methods:
By the end of this report, you should be able to:
# Load the datasets
# Performance standard data (summarized annual estimates)
ps_data <- read_csv("edi.648.8/wetland_ps_fish_abundance_and_richness-2025-08-27_14-22-49.csv")
# Raw enclosure trap data (gobies)
enclosure_data <- read_csv("edi.647.8/wetland_ts_fish_enclosure-2025-08-27_14-40-54.csv")
# Raw beach seine data (larger fishes)
seine_data <- read_csv("edi.647.8/wetland_ts_fish_seine-2025-08-27_14-42-14.csv")
# Filter to Carpinteria Salt Marsh only
csm_ps <- ps_data %>%
filter(wetland_code == "CSM")
csm_enclosure <- enclosure_data %>%
filter(wetland_code == "CSM")
csm_seine <- seine_data %>%
filter(wetland_code == "CSM")
# Quick summary of the CSM dataset
data_summary <- tibble(
`Data Type` = c("Performance Summary", "Enclosure Traps", "Beach Seines"),
`Years` = c(
paste(range(csm_ps$year), collapse = "-"),
paste(range(csm_enclosure$year), collapse = "-"),
paste(range(csm_seine$year), collapse = "-")
),
`Total Records` = c(
nrow(csm_ps),
nrow(csm_enclosure),
nrow(csm_seine)
),
`Sampling Locations` = c(
n_distinct(csm_ps$tc_mc_code),
n_distinct(csm_enclosure$tc_mc_code),
n_distinct(csm_seine$tc_mc_code)
)
)
datatable(data_summary,
caption = "Summary of Carpinteria Salt Marsh Fish Data",
options = list(dom = 't', pageLength = 5),
class = 'cell-border stripe')
What fish species call Carpinteria Salt Marsh home? Let’s build a comprehensive species list from both sampling methods.
# Get species from enclosure data
enclosure_species <- csm_enclosure %>%
filter(count > 0) %>%
distinct(species_code, genus_name, species_name) %>%
mutate(method = "Enclosure")
# Get species from seine data
seine_species <- csm_seine %>%
filter(count > 0) %>%
distinct(species_code, genus_name, species_name) %>%
mutate(method = "Seine")
# Combine and identify which method caught each species
all_species <- bind_rows(enclosure_species, seine_species) %>%
group_by(species_code, genus_name, species_name) %>%
summarize(
caught_by = paste(unique(method), collapse = " & "),
.groups = "drop"
) %>%
arrange(genus_name, species_name)
# Add common names from FishBase (comprehensive lookup)
common_names <- tribble(
~species_code, ~common_name, ~family,
# Gobies (Gobiidae)
"ACFV", "Yellowfin Goby", "Gobiidae",
"CLIO", "Arrow Goby", "Gobiidae",
"CTSA", "Longtail Goby", "Gobiidae",
"GIMI", "Longjaw Mudsucker", "Gobiidae",
"ILGI", "Cheekspot Goby", "Gobiidae",
"QUYC", "American Shadow Goby", "Gobiidae",
# Silversides (Atherinopsidae)
"ATAF", "Topsmelt Silverside", "Atherinopsidae",
"ATCA", "Jack Silverside", "Atherinopsidae",
"LETN", "California Grunion", "Atherinopsidae",
"MEBE", "Inland Silverside", "Atherinopsidae",
# Killifishes & Livebearers
"FUPA", "California Killifish", "Fundulidae",
"GAAF", "Mosquitofish", "Poeciliidae",
"POLA", "Sailfin Molly", "Poeciliidae",
# Anchovies & Herrings (Clupeiformes)
"ANCO", "Deepbody Anchovy", "Engraulidae",
"ENMO", "Californian Anchovy", "Engraulidae",
"CLHA", "Pacific Herring", "Clupeidae",
"SASA", "Pacific Sardine", "Clupeidae",
# Flatfishes (Pleuronectiformes)
"HYGU", "Diamond Turbot", "Pleuronectidae",
"PACA", "California Flounder", "Paralichthyidae",
"PLCO", "C-O Sole", "Pleuronectidae",
"PLRI", "Spotted Turbot", "Pleuronectidae",
"SYAT", "California Tonguefish", "Cynoglossidae",
# Pipefishes (Syngnathidae)
"COAC", "Snubnose Pipefish", "Syngnathidae",
"SYAU", "Barred Pipefish", "Syngnathidae",
"SYLE", "Bay Pipefish", "Syngnathidae",
# Sea Basses (Serranidae)
"PACL", "Kelp Bass", "Serranidae",
"PAMA", "Spotted Sand Bass", "Serranidae",
"PANE", "Barred Sand Bass", "Serranidae",
# Croakers (Sciaenidae)
"MEUN", "California Kingcroaker", "Sciaenidae",
"SEPO", "Queen Croaker", "Sciaenidae",
"UMRO", "Yellowfin Drum", "Sciaenidae",
# Blennies (Blenniidae)
"HYGE", "Bay Blenny", "Blenniidae",
"HYGI", "Rockpool Blenny", "Blenniidae",
# Kelpfishes & Clinids (Clinidae)
"GIME", "Striped Kelpfish", "Clinidae",
"HERO", "Giant Kelpfish", "Clinidae",
"NEBL", "Sarcastic Fringehead", "Chaenopsidae",
"NEUN", "Onespot Fringehead", "Chaenopsidae",
# Other Bony Fishes
"MUCE", "Flathead Grey Mullet", "Mugilidae",
"LEAR", "Pacific Staghorn Sculpin", "Cottidae",
"CYAG", "Shiner Perch", "Embiotocidae",
"GINI", "Opaleye", "Kyphosidae",
"HEAZ", "Zebra-perch Sea Chub", "Kyphosidae",
"STEX", "Californian Needlefish", "Belonidae",
"SPAR", "Pacific Barracuda", "Sphyraenidae",
"SPAN", "Bullseye Puffer", "Tetraodontidae",
"POMY", "Specklefin Midshipman", "Batrachoididae",
"SERA", "Grass Rockfish", "Sebastidae",
"ANDA", "Xantic Sargo", "Haemulidae",
"XECA", "Californian Salema", "Haemulidae",
# Sharks (Elasmobranchs)
"TRSE", "Leopard Shark", "Triakidae",
"MUCA", "Gray Smoothhound", "Triakidae",
# Rays (Batoidea)
"MYCA", "Bat Eagle Ray", "Myliobatidae",
"URHA", "Haller's Round Ray", "Urotrygonidae",
"GYMA", "California Butterfly Ray", "Gymnuridae",
"PLTR", "Thornback Guitarfish", "Platyrhinidae",
"RHPR", "Shovelnose Guitarfish", "Rhinobatidae"
)
species_table <- all_species %>%
left_join(common_names, by = "species_code") %>%
mutate(
common_name = ifelse(is.na(common_name), "Unknown", common_name),
family = ifelse(is.na(family), "Other", family)
) %>%
select(common_name, genus_name, species_name, family, caught_by) %>%
rename(
`Common Name` = common_name,
`Genus` = genus_name,
`Species` = species_name,
`Family` = family,
`Sampling Method` = caught_by
)
n_species <- nrow(species_table)
# Interactive searchable species table
datatable(species_table,
caption = "Fish Species Recorded at Carpinteria Salt Marsh (2012-2024) - Click columns to sort, use search box to filter",
filter = 'top',
options = list(
pageLength = 10,
autoWidth = TRUE,
columnDefs = list(list(className = 'dt-center', targets = "_all"))
),
class = 'cell-border stripe hover') %>%
formatStyle('Family',
backgroundColor = styleEqual(
c("Gobiidae", "Atherinopsidae", "Fundulidae"),
c("#e3f2fd", "#fff3e0", "#e8f5e9")
))
Not all species are equally abundant. Let’s identify the most common fishes.
# Calculate total abundance by species from enclosure data
enclosure_totals <- csm_enclosure %>%
group_by(species_code, genus_name, species_name) %>%
summarize(
total_count = sum(count, na.rm = TRUE),
n_samples = n_distinct(paste(year, date, tc_mc_code)),
.groups = "drop"
) %>%
filter(total_count > 0) %>%
left_join(common_names, by = "species_code") %>%
mutate(
common_name = ifelse(is.na(common_name), paste(genus_name, species_name), common_name),
prop = total_count / sum(total_count)
) %>%
arrange(desc(total_count))
# Calculate total abundance by species from seine data
seine_totals <- csm_seine %>%
group_by(species_code, genus_name, species_name) %>%
summarize(
total_count = sum(count, na.rm = TRUE),
n_samples = n_distinct(paste(year, date, tc_mc_code)),
.groups = "drop"
) %>%
filter(total_count > 0) %>%
left_join(common_names, by = "species_code") %>%
mutate(
common_name = ifelse(is.na(common_name), paste(genus_name, species_name), common_name),
prop = total_count / sum(total_count)
) %>%
arrange(desc(total_count))
# Interactive bar chart with Highcharter
goby_data <- enclosure_totals %>%
slice_head(n = 5) %>%
mutate(family = ifelse(is.na(family), "Other", family))
highchart() %>%
hc_chart(type = "bar") %>%
hc_title(text = "Most Abundant Gobies in Carpinteria Salt Marsh") %>%
hc_subtitle(text = "Enclosure trap sampling (2012-2024) - Hover for details") %>%
hc_xAxis(categories = goby_data$common_name) %>%
hc_yAxis(title = list(text = "Total Individuals Captured")) %>%
hc_add_series(
name = "Count",
data = goby_data$total_count,
colorByPoint = TRUE
) %>%
hc_tooltip(
pointFormat = "<b>{point.category}</b><br>Total caught: <b>{point.y:,.0f}</b><br>Proportion: <b>{point.percentage:.1f}%</b>",
headerFormat = ""
) %>%
hc_plotOptions(
bar = list(
dataLabels = list(enabled = TRUE, format = "{point.y:,.0f}")
)
) %>%
hc_add_theme(hc_theme_csm) %>%
hc_legend(enabled = FALSE)
Key Finding: The Arrow Goby (Clevelandia ios) is by far the most abundant fish in the salt marsh, followed by the Cheekspot Goby and Shadow Goby. These small gobies are specialists of soft-bottom estuarine habitats.
# Interactive treemap showing species composition
seine_top <- seine_totals %>%
slice_head(n = 15) %>%
mutate(family = ifelse(is.na(family), "Other", family))
highchart() %>%
hc_chart(type = "treemap") %>%
hc_title(text = "Fish Community Composition - Seine Catches") %>%
hc_subtitle(text = "Size represents total abundance - Click to explore") %>%
hc_add_series(
data = list_parse(
seine_top %>%
transmute(
name = common_name,
value = total_count,
colorValue = log10(total_count + 1)
)
),
layoutAlgorithm = "squarified",
allowTraversingTree = TRUE
) %>%
hc_colorAxis(
minColor = "#f7fbff",
maxColor = "#08519c",
type = "linear"
) %>%
hc_tooltip(
pointFormat = "<b>{point.name}</b><br>Total: {point.value:,.0f} individuals"
) %>%
hc_add_theme(hc_theme_csm)
Key Finding: Topsmelt (Atherinops affinis) and California Killifish (Fundulus parvipinnis) dominate seine catches. Both species are highly adapted to estuarine conditions with fluctuating salinity and temperature.
Salt marshes contain diverse microhabitats. At Carpinteria, sampling occurs in two distinct habitat types:
# Summarize by habitat
habitat_summary <- csm_ps %>%
mutate(habitat = ifelse(habitat_code == "TC", "Tidal Creek", "Main Channel")) %>%
group_by(year, habitat) %>%
summarize(
mean_density = mean(count_per_m2, na.rm = TRUE),
se_density = sd(count_per_m2, na.rm = TRUE) / sqrt(n()),
mean_richness = mean(species_count, na.rm = TRUE),
se_richness = sd(species_count, na.rm = TRUE) / sqrt(n()),
n = n(),
.groups = "drop"
)
# Interactive time series with Plotly
p_density <- habitat_summary %>%
ggplot(aes(x = year, y = mean_density, color = habitat,
text = paste0("Year: ", year,
"<br>Habitat: ", habitat,
"<br>Mean Density: ", round(mean_density, 2), " fish/m²",
"<br>SE: ±", round(se_density, 2)))) +
geom_ribbon(aes(ymin = mean_density - se_density,
ymax = mean_density + se_density,
fill = habitat),
alpha = 0.2, color = NA) +
geom_line(size = 1.2) +
geom_point(size = 3) +
scale_color_manual(values = c("Tidal Creek" = "#e74c3c", "Main Channel" = "#3498db")) +
scale_fill_manual(values = c("Tidal Creek" = "#e74c3c", "Main Channel" = "#3498db")) +
scale_x_continuous(breaks = seq(2012, 2024, 2)) +
labs(
title = "Fish Density in Carpinteria Salt Marsh Habitats",
subtitle = "Hover over points for details",
x = "Year",
y = "Fish Density (individuals/m²)",
color = "Habitat",
fill = "Habitat"
) +
theme(legend.position = "top")
ggplotly(p_density, tooltip = "text") %>%
layout(hoverlabel = list(bgcolor = "white"))
# Highcharter dual-axis comparison
hc_habitat <- highchart() %>%
hc_chart(type = "line") %>%
hc_title(text = "Species Richness by Habitat Over Time") %>%
hc_subtitle(text = "Compare tidal creeks vs main channels") %>%
hc_xAxis(categories = unique(habitat_summary$year)) %>%
hc_yAxis(title = list(text = "Mean Number of Species")) %>%
hc_add_series(
name = "Tidal Creek",
data = habitat_summary %>% filter(habitat == "Tidal Creek") %>% pull(mean_richness),
color = "#e74c3c"
) %>%
hc_add_series(
name = "Main Channel",
data = habitat_summary %>% filter(habitat == "Main Channel") %>% pull(mean_richness),
color = "#3498db"
) %>%
hc_tooltip(
shared = TRUE,
crosshairs = TRUE,
pointFormat = "{series.name}: <b>{point.y:.1f}</b> species<br>"
) %>%
hc_plotOptions(
line = list(
marker = list(enabled = TRUE, radius = 5)
)
) %>%
hc_add_theme(hc_theme_csm)
hc_habitat
habitat_overall <- csm_ps %>%
mutate(habitat = ifelse(habitat_code == "TC", "Tidal Creek", "Main Channel")) %>%
group_by(habitat) %>%
summarize(
`Mean Density (fish/m²)` = round(mean(count_per_m2, na.rm = TRUE), 2),
`SD Density` = round(sd(count_per_m2, na.rm = TRUE), 2),
`Max Density` = round(max(count_per_m2, na.rm = TRUE), 2),
`Mean Richness` = round(mean(species_count, na.rm = TRUE), 1),
`Max Richness` = max(species_count, na.rm = TRUE),
`N Samples` = n(),
.groups = "drop"
)
datatable(habitat_overall,
caption = "Summary Statistics by Habitat Type (2012-2024)",
options = list(dom = 't'),
class = 'cell-border stripe') %>%
formatStyle(
'Mean Density (fish/m²)',
background = styleColorBar(c(0, max(habitat_overall$`Mean Density (fish/m²)`)), '#3498db'),
backgroundSize = '98% 88%',
backgroundRepeat = 'no-repeat',
backgroundPosition = 'center'
)
Discussion Question: Why might tidal creeks have higher fish densities but similar or lower species richness compared to main channels?
Fish populations in estuaries can vary dramatically from year to year due to environmental conditions, recruitment variability, and other factors.
annual_summary <- csm_ps %>%
group_by(year) %>%
summarize(
total_density = sum(count_per_m2, na.rm = TRUE),
mean_density = mean(count_per_m2, na.rm = TRUE),
se_density = sd(count_per_m2, na.rm = TRUE) / sqrt(n()),
mean_richness = mean(species_count, na.rm = TRUE),
se_richness = sd(species_count, na.rm = TRUE) / sqrt(n()),
n_locations = n(),
.groups = "drop"
)
# Interactive combined chart
highchart() %>%
hc_chart(zoomType = "x") %>%
hc_title(text = "Long-term Trends in Fish Community") %>%
hc_subtitle(text = "Drag to zoom, click legend to toggle series") %>%
hc_xAxis(categories = annual_summary$year) %>%
hc_yAxis_multiples(
list(
title = list(text = "Mean Density (fish/m²)", style = list(color = "#3498db")),
labels = list(style = list(color = "#3498db"))
),
list(
title = list(text = "Mean Species Richness", style = list(color = "#e74c3c")),
labels = list(style = list(color = "#e74c3c")),
opposite = TRUE
)
) %>%
hc_add_series(
name = "Fish Density",
data = round(annual_summary$mean_density, 2),
type = "column",
color = "#3498db",
yAxis = 0
) %>%
hc_add_series(
name = "Species Richness",
data = round(annual_summary$mean_richness, 1),
type = "spline",
color = "#e74c3c",
yAxis = 1,
marker = list(radius = 5)
) %>%
hc_tooltip(
shared = TRUE,
crosshairs = TRUE
) %>%
hc_add_theme(hc_theme_csm)
# Interactive boxplots by year
csm_ps %>%
plot_ly(x = ~factor(year), y = ~count_per_m2, type = "box",
color = ~factor(year), colors = viridis(13),
hoverinfo = "y") %>%
layout(
title = list(text = "Distribution of Fish Densities by Year<br><sub>Hover over boxes for statistics</sub>"),
xaxis = list(title = "Year"),
yaxis = list(title = "Fish Density (individuals/m²)"),
showlegend = FALSE
)
# Create linked dataset with crosstalk
csm_shared <- SharedData$new(
csm_ps %>%
mutate(
habitat = ifelse(habitat_code == "TC", "Tidal Creek", "Main Channel"),
location = tc_mc_code
)
)
# Interactive scatter with crosstalk filtering
bscols(
widths = c(3, 9),
list(
filter_checkbox("habitat", "Habitat", csm_shared, ~habitat),
filter_slider("year", "Year", csm_shared, ~year, step = 1, width = "100%"),
filter_slider("density", "Density", csm_shared, ~count_per_m2, width = "100%")
),
plot_ly(csm_shared, x = ~species_count, y = ~count_per_m2,
color = ~habitat, colors = c("#3498db", "#e74c3c"),
text = ~paste0("Location: ", location,
"<br>Year: ", year,
"<br>Density: ", round(count_per_m2, 2),
"<br>Species: ", species_count),
hoverinfo = "text",
type = "scatter", mode = "markers",
marker = list(size = 10, opacity = 0.7)) %>%
layout(
title = "Explore: Density vs Species Richness",
xaxis = list(title = "Number of Species"),
yaxis = list(title = "Fish Density (individuals/m²)")
)
)
# Find the highest and lowest years
peak_year <- annual_summary %>% slice_max(mean_density, n = 1)
low_year <- annual_summary %>% slice_min(mean_density, n = 1)
Question for Discussion: What environmental factors might explain this variation? Consider: El Niño events, drought conditions, marine heatwaves, and local management changes.
Get to know the fishes of Carpinteria Salt Marsh! Photos from iNaturalist community scientists.
The most abundant fishes in the salt marsh are small, bottom-dwelling gobies.
Clevelandia ios - The most abundant fish in the marsh. Lives in burrows made by ghost shrimp.
Gillichthys mirabilis - Can breathe air and survive out of water for extended periods!
Acanthogobius flavimanus - An introduced species from Asia, now common in California estuaries.
Surface-dwelling schooling fishes that are important prey for larger predators.
Atherinops affinis - A key forage fish species. Forms large schools near the surface.
Fundulus parvipinnis - Endemic to California. Tolerates extreme salinity changes.
Masters of camouflage that live on the bottom.
Paralichthys californicus - An important commercial and recreational species. Juveniles use the marsh as nursery habitat.
Engraulis mordax - A keystone forage species. Feeds whales, seabirds, and larger fish.
Girella nigricans - Named for its beautiful blue-green eyes. Primarily herbivorous.
Paralabrax clathratus - California’s most popular sport fish. Juveniles use estuaries.
Mugil cephalus - A cosmopolitan species found worldwide. Feeds on algae and detritus.
Neoclinus blanchardi - Famous for its dramatic territorial displays with enormous jaws!
Let’s take a closer look at some key species found in Carpinteria Salt Marsh.
arrow_goby <- csm_enclosure %>%
filter(species_code == "CLIO") %>%
group_by(year, habitat_code) %>%
summarize(
total_count = sum(count, na.rm = TRUE),
mean_count = mean(count, na.rm = TRUE),
.groups = "drop"
)
The Arrow Goby is a small (< 5 cm) benthic fish endemic to the Pacific coast of North America. Key traits include: - Habitat: Soft mud bottoms, often associated with ghost shrimp burrows - Diet: Small invertebrates, detritus - Reproduction: Males guard eggs in burrow chambers - Ecological Role: Important prey for larger fishes and wading birds
arrow_goby_wide <- arrow_goby %>%
mutate(habitat = ifelse(habitat_code == "TC", "Tidal Creek", "Main Channel")) %>%
pivot_wider(names_from = habitat, values_from = c(total_count, mean_count), values_fill = 0)
highchart() %>%
hc_chart(type = "column") %>%
hc_title(text = "Arrow Goby Abundance at Carpinteria Salt Marsh") %>%
hc_subtitle(text = "Stacked by habitat - hover for details") %>%
hc_xAxis(categories = unique(arrow_goby$year)) %>%
hc_yAxis(title = list(text = "Total Count"), stackLabels = list(enabled = TRUE)) %>%
hc_plotOptions(column = list(stacking = "normal")) %>%
hc_add_series(
name = "Tidal Creek",
data = arrow_goby %>% filter(habitat_code == "TC") %>% arrange(year) %>% pull(total_count),
color = "#e74c3c"
) %>%
hc_add_series(
name = "Main Channel",
data = arrow_goby %>% filter(habitat_code == "BNMC") %>% arrange(year) %>% pull(total_count),
color = "#3498db"
) %>%
hc_tooltip(
shared = TRUE,
pointFormat = "{series.name}: <b>{point.y:,.0f}</b><br>"
) %>%
hc_add_theme(hc_theme_csm)
killifish <- csm_seine %>%
filter(species_code == "FUPA") %>%
group_by(year, habitat_code) %>%
summarize(
total_count = sum(count, na.rm = TRUE),
.groups = "drop"
)
The California Killifish is a euryhaline species (tolerant of wide salinity ranges) that is endemic to California and Baja California estuaries.
killifish %>%
mutate(habitat = ifelse(habitat_code == "TC", "Tidal Creek", "Main Channel")) %>%
plot_ly(x = ~year, y = ~total_count, color = ~habitat,
colors = c("#3498db", "#e74c3c"),
type = "bar",
hoverinfo = "text",
text = ~paste0("Year: ", year, "<br>",
"Habitat: ", habitat, "<br>",
"Count: ", comma(total_count))) %>%
layout(
title = "California Killifish Abundance by Habitat",
barmode = "group",
xaxis = list(title = "Year"),
yaxis = list(title = "Total Count")
)
topsmelt <- csm_seine %>%
filter(species_code == "ATAF") %>%
group_by(year) %>%
summarize(
total_count = sum(count, na.rm = TRUE),
.groups = "drop"
)
Topsmelt are schooling silversides that are abundant in California bays and estuaries.
highchart() %>%
hc_chart(type = "areaspline") %>%
hc_title(text = "Topsmelt Abundance at Carpinteria Salt Marsh") %>%
hc_subtitle(text = "Note the extreme interannual variability") %>%
hc_xAxis(categories = topsmelt$year) %>%
hc_yAxis(title = list(text = "Total Count")) %>%
hc_add_series(
name = "Topsmelt",
data = topsmelt$total_count,
color = "#f39c12",
fillOpacity = 0.5
) %>%
hc_tooltip(
pointFormat = "Year {point.category}: <b>{point.y:,.0f}</b> individuals"
) %>%
hc_add_theme(hc_theme_csm)
Fish species can be grouped into ecological guilds based on their habitat use and life history strategies.
# Assign guilds to key species
guild_assignments <- tribble(
~species_code, ~guild, ~description,
"CLIO", "Resident", "Spends entire life in estuary",
"ILGI", "Resident", "Spends entire life in estuary",
"QUYC", "Resident", "Spends entire life in estuary",
"GIMI", "Resident", "Spends entire life in estuary",
"FUPA", "Resident", "Spends entire life in estuary",
"ATAF", "Marine Migrant", "Uses estuary seasonally",
"PACA", "Marine Migrant", "Juvenile nursery habitat",
"HYGU", "Marine Migrant", "Juvenile nursery habitat",
"SYLE", "Resident", "Spends entire life in estuary",
"MUCE", "Marine Migrant", "Adults enter to feed",
"MYCA", "Marine Migrant", "Foraging habitat",
"ANCO", "Marine Migrant", "Uses estuary seasonally"
)
# Calculate proportions by guild from seine data
guild_summary <- csm_seine %>%
left_join(guild_assignments, by = "species_code") %>%
filter(!is.na(guild)) %>%
group_by(year, guild) %>%
summarize(total_count = sum(count, na.rm = TRUE), .groups = "drop") %>%
group_by(year) %>%
mutate(proportion = total_count / sum(total_count) * 100)
# Interactive stacked area chart
highchart() %>%
hc_chart(type = "area") %>%
hc_title(text = "Ecological Guild Composition Over Time") %>%
hc_subtitle(text = "Proportion of resident vs marine migrant species") %>%
hc_xAxis(categories = unique(guild_summary$year)) %>%
hc_yAxis(
title = list(text = "Percentage of Total Catch"),
max = 100,
labels = list(format = "{value}%")
) %>%
hc_plotOptions(area = list(stacking = "percent")) %>%
hc_add_series(
name = "Resident",
data = guild_summary %>% filter(guild == "Resident") %>% arrange(year) %>% pull(total_count),
color = "#27ae60"
) %>%
hc_add_series(
name = "Marine Migrant",
data = guild_summary %>% filter(guild == "Marine Migrant") %>% arrange(year) %>% pull(total_count),
color = "#3498db"
) %>%
hc_tooltip(
shared = TRUE,
pointFormat = "{series.name}: <b>{point.percentage:.1f}%</b> ({point.y:,.0f})<br>"
) %>%
hc_add_theme(hc_theme_csm)
overall_stats <- csm_ps %>%
summarize(
`Years of Data` = paste(min(year), "-", max(year)),
`Total Sampling Events` = n(),
`Mean Density (fish/m²)` = round(mean(count_per_m2), 2),
`Max Density (fish/m²)` = round(max(count_per_m2), 2),
`Mean Species Richness` = round(mean(species_count), 1),
`Max Species Richness` = max(species_count)
)
# Create nice summary cards with HTML
div(class = "row",
div(class = "col-md-4",
div(class = "panel panel-primary",
div(class = "panel-heading", "Years of Monitoring"),
div(class = "panel-body", style = "font-size: 24px; text-align: center;",
overall_stats$`Years of Data`))),
div(class = "col-md-4",
div(class = "panel panel-success",
div(class = "panel-heading", "Mean Fish Density"),
div(class = "panel-body", style = "font-size: 24px; text-align: center;",
paste(overall_stats$`Mean Density (fish/m²)`, "fish/m²")))),
div(class = "col-md-4",
div(class = "panel panel-info",
div(class = "panel-heading", "Mean Species Richness"),
div(class = "panel-body", style = "font-size: 24px; text-align: center;",
paste(overall_stats$`Mean Species Richness`, "species"))))
)
# Categorize species by abundance
all_species_counts <- bind_rows(
csm_enclosure %>%
group_by(species_code) %>%
summarize(total = sum(count), .groups = "drop"),
csm_seine %>%
group_by(species_code) %>%
summarize(total = sum(count), .groups = "drop")
) %>%
group_by(species_code) %>%
summarize(total = sum(total), .groups = "drop") %>%
left_join(common_names, by = "species_code") %>%
mutate(
category = case_when(
total > 10000 ~ "Dominant (>10,000)",
total > 1000 ~ "Common (1,000-10,000)",
total > 100 ~ "Moderate (100-1,000)",
total > 10 ~ "Uncommon (10-100)",
TRUE ~ "Rare (<10)"
)
)
category_summary <- all_species_counts %>%
group_by(category) %>%
summarize(
`Number of Species` = n(),
`Total Individuals` = sum(total),
.groups = "drop"
) %>%
arrange(desc(`Total Individuals`))
# Interactive pie chart of abundance categories
highchart() %>%
hc_chart(type = "pie") %>%
hc_title(text = "Species Distribution by Abundance Category") %>%
hc_subtitle(text = "Click slices to see details") %>%
hc_add_series(
name = "Species",
data = list_parse(
category_summary %>%
transmute(
name = category,
y = `Number of Species`,
individuals = `Total Individuals`
)
)
) %>%
hc_tooltip(
pointFormat = "<b>{point.name}</b><br>Species: {point.y}<br>Total Individuals: {point.individuals:,.0f}"
) %>%
hc_plotOptions(
pie = list(
allowPointSelect = TRUE,
cursor = "pointer",
dataLabels = list(
enabled = TRUE,
format = "{point.name}: {point.y} species"
)
)
) %>%
hc_add_theme(hc_theme_csm)
High Diversity: At least 43 fish species use Carpinteria Salt Marsh, from tiny gobies to large elasmobranchs
Goby Dominance: Small benthic gobies, especially the Arrow Goby, numerically dominate the fish community
Habitat Partitioning: Tidal creeks and main channels support different fish densities and compositions
Temporal Variability: Fish populations fluctuate dramatically between years, likely driven by environmental conditions and recruitment
Estuarine Specialists: The community is dominated by resident species highly adapted to fluctuating estuarine conditions
Why are gobies so abundant in salt marshes compared to other habitats?
How might climate change affect salt marsh fish communities?
What is the value of long-term monitoring datasets like this one?
How do sampling methods influence our perception of community composition?
Allen, L.G., M.H. Horn, F.A. Edmands II, and C.A. Usui. 1983. Structure and seasonal dynamics of the fish assemblage in the Cabrillo Beach area of Los Angeles Harbor, California. Bulletin of the Southern California Academy of Sciences 82:47-70.
Horn, M.H., and L.G. Allen. 1985. Fish community ecology in southern California bays and estuaries. Pages 169-190 in A. Yanez-Arancibia, editor. Fish community ecology in estuaries and coastal lagoons: Towards ecosystem integration. UNAM Press, Mexico City.
Steele, M.A., S.C. Schroeter, and H.M. Page. 2006. Sampling characteristics and biases of enclosure traps for sampling fishes in estuaries. Estuaries and Coasts 29:630-638.
SONGS Mitigation Monitoring Program: http://marinemitigation.msi.ucsb.edu/
Report generated on 2025-12-01 using R version R version 4.5.1 (2025-06-13)